#include"memory.h"
#include"os_constant.h"
#include"stdlib.h"
#include"asm_utils.h"
#include"stdio.h"
#include"program.h"
#include"os_modules.h"

MemoryManager::MemoryManager()
{
    initialize();
}
void MemoryManager::initialize()
{
    //LRU初始化
    frame_size=0;
    for(int i=0;i<MAX_LRU_BLOCK_SIZE;i++)
    {
        frame[i]=LRU_Unit();
    }
    this->totalMemory = 0;
    this->totalMemory = getTotalMemory();
    //预留的内存
    int usedMemory = 256*PAGE_SIZE+0x100000;
    if(this->totalMemory < usedMemory)
    {
        printf("memory is too small,halt...\n");
        asm_halt();
    }
    //剩余空闲内存
    int  freeMemory = this->totalMemory - usedMemory;

    int freePages = freeMemory/PAGE_SIZE;
    int kernelPages = freePages/2;
    int userPages = freePages-kernelPages;

    int kernelPhysicalStartAddress = usedMemory;
    int userPhysicalStartAddress = usedMemory + kernelPages*PAGE_SIZE;

    int kernelPhysicalBitmapStart = BITMAP_START_ADDRESS;
    int userPhysicalBitmapStart = kernelPhysicalBitmapStart + ceil(kernelPages,8);
    int kernelVirtualBitmapStart = userPhysicalBitmapStart + ceil(userPages,8);

    kernelPhysical.initialize((char*)kernelPhysicalBitmapStart,kernelPages,kernelPhysicalStartAddress);
    userPhysical.initialize((char*)userPhysicalBitmapStart,userPages,userPhysicalStartAddress);
    kernelVirtual.initialize((char*)kernelVirtualBitmapStart,kernelPages,KERNEL_VIRTUAL_START);

    printf("total memory: %d bytes (%d MB)\n",
            this->totalMemory,this->totalMemory/1024/1024);
    printf("kernel pool\n"
            "   start address: 0x%x\n"
            "   total pages: %d(%d MB)\n"
            "   bitmap start address: 0x%x\n",
            kernelPhysicalStartAddress,
            kernelPages,kernelPages*PAGE_SIZE/1024/1024,
            kernelPhysicalBitmapStart);
    printf("user pool\n"
            "   start address: 0x%x\n"
            "   total pages: %d(%d MB)\n"
            "   bitmap start address: 0x%x\n",
            userPhysicalStartAddress,
            userPages,userPages*PAGE_SIZE/1024/1024,
            userPhysicalBitmapStart);
    printf("kernel virtual pool\n"
            "   start address: 0x%x\n"
            "   total pages: %d(%d MB)\n"
            "   bitmap start address: 0x%x\n",
            KERNEL_VIRTUAL_START,
            userPages,kernelPages*PAGE_SIZE/1024/1024,
            kernelVirtualBitmapStart);
}
int MemoryManager::allocatePhysicalPages(enum AddressPoolType type,const int count)
{
    int start = -1;
    if(type==AddressPoolType::KERNEL)
    {
        start = kernelPhysical.allocate(count);
    }else if(type==AddressPoolType::USER){
        start = userPhysical.allocate(count);
    }
    return (start==-1)? 0:start;
}
void MemoryManager::releasePhysicalPages(enum AddressPoolType type,const int addressStart,const int count)
{
    if(type==AddressPoolType::KERNEL)
    {
        kernelPhysical.release(addressStart,count);
    }else if(type==AddressPoolType::USER){
        userPhysical.release(addressStart,count);
    }
}
int MemoryManager::getTotalMemory()
{
    if(!this->totalMemory)
    {
        int memory = *((int*)MEMORY_SIZE_ADDRESS);
        //ax保存内容
        int low = memory & 0xffff;
        //bx保存内容
        int high = (memory>>16) & 0xffff;
        this->totalMemory = low*1024+high*64*1024;
    }
    return this->totalMemory;
}
int MemoryManager::allocatePages(enum AddressPoolType type,const int count)
{
    //第一步，虚拟地址池分配若干虚拟页
    int virtualAddress = allocateVirtualPages(type,count);
    if(!virtualAddress)
    {
        printf("allocate pages count %d failed...\n",count);
        while(1)
        {
            virtualAddress=allocateVirtualPages(type,count);
            if(virtualAddress)break;
            if(frame_size<=0)return 0;
            //释放帧
            int index=0;
            for(int i=0;i<frame_size;i++)
            {
                if(frame[i].time>frame[index].time)index=i;
            }
            printf("address %x size %d  ",frame[index].address,frame[index].size);
            releasePages(type,frame[index].address,frame[index].size);
            for(int i=index;i<frame_size-1;i++)
            {
                frame[i]=frame[i+1];
            }
            frame_size--;
            printf("release a block...\n");
        }
    }
    bool flag;
    int physicalPageAddress;
    int vaddress = virtualAddress;
    //依次为虚拟页分配物理页
    for(int i=0; i<count; i++,vaddress += PAGE_SIZE)
    {
        flag = false;
        //第二步，从物理地址池分配物理页
        physicalPageAddress = allocatePhysicalPages(type,1);
        if(physicalPageAddress)
        {
            // printf("count %d:allocate ----virtual page 0x%x physical page 0x%x\n",count,vaddress,physicalPageAddress);
            //第三步，为虚拟页建立页目录项和页表项，使虚拟页内的地址变换到物理页内
            flag = connectPhysicalVirtualPage(vaddress,physicalPageAddress);
        }else{
            flag = false;
        }
        //分配失败，释放前面已经分配的虚拟页和物理页表
        if(!flag)
        {
            //前i个页表已经指定了物理页
            releasePages(type,virtualAddress,i);
            //剩余的页表未指定物理页
            releaseVirtualPages(type,virtualAddress+i*PAGE_SIZE,count-i);
            return 0;
        }
    }
    //增加时间，新增块
    for(int i=0;i<frame_size;i++) frame[i].time++;
    if(frame_size>=MAX_LRU_BLOCK_SIZE)
    {
        //释放帧
            int index=0;
            for(int i=0;i<frame_size;i++)
            {
                if(frame[i].time>frame[index].time)index=i;
            }
            printf("address %x size %d  ",frame[index].address,frame[index].size);
            releasePages(type,frame[index].address,frame[index].size);
            for(int i=index;i<frame_size-1;i++)
            {
                frame[i]=frame[i+1];
            }
            frame_size--;
            printf("release a block...\n");
    }
    frame[frame_size]=LRU_Unit(virtualAddress,count,0);
    frame_size++;
    return virtualAddress;
}
int MemoryManager::allocateVirtualPages(enum AddressPoolType type,const int count)
{
    int start = -1;
    if(type==AddressPoolType::KERNEL)
    {
        start = kernelVirtual.allocate(count);
    }else if(type==AddressPoolType::USER){
        start = programManager.running->userVirtual.allocate(count);
    }
    return (start==-1)? 0:start;
}
bool MemoryManager::connectPhysicalVirtualPage(const int virtualAddress,const int physicalPageAddress)
{
    //计算虚拟地址对应的页目录项和页表项
    int* pde = (int*)toPDE(virtualAddress);
    int* pte = (int*)toPTE(virtualAddress);
    //页目录项无对应的页表，先分配一个页表
    if(!(*pde&0x00000001))
    {
        //从内核物理地址空间分配一个页表
        int page = allocatePhysicalPages(AddressPoolType::KERNEL,1);
        if(!page)return false;
        //使页目录表指向页表
        *pde = page | 0x7;
        //初始化页表
        char* pagePtr = (char*)(((int)pte) & 0xfffff000);
        memset(pagePtr,0,PAGE_SIZE);
    }
    //使页表项指向物理页
    *pte = physicalPageAddress | 0x7;
    // printf("*pde = 0x%x *pte = 0x%x\n",*pde,*pte);
    return true;
}
int MemoryManager::toPDE(const int virtualAddress)
{
    return (0xfffff000 + (((virtualAddress & 0xffc00000)>>22)*4));
}
int MemoryManager::toPTE(const int virtualAddress)
{
    return (0xffc00000 + ((virtualAddress & 0xffc00000)>>10) + (((virtualAddress & 0x003ff000)>>12)*4));
}
void MemoryManager::releasePages(enum AddressPoolType type,const int virtualAddress,const int count)
{
    int vaddress = virtualAddress;
    int* pte;
    for(int i=0; i<count; i++,vaddress += PAGE_SIZE)
    {
        //1.释放物理页
        releasePhysicalPages(type,virtualAddressToPhysicalAddress(vaddress),1);
        //设置页表项为不存在，防止释放后被再次使用
        pte = (int*)toPTE(vaddress);
        *pte = 0;
    };
    //2.释放虚拟页
    releaseVirtualPages(type,virtualAddress,count);
    // asm_init_page_reg((int*)PAGE_DIRECTORY);
}
int MemoryManager::virtualAddressToPhysicalAddress(int virtualAddress)
{
    int* pte = (int*)toPTE(virtualAddress);
    int page = (*pte) & 0xfffff000;
    int offset = virtualAddress & 0xfff;
    return (page + offset);
}
void MemoryManager::releaseVirtualPages(enum AddressPoolType type,const int virtualAddress,const int count)
{
    if(type==AddressPoolType::KERNEL)
    {
        kernelVirtual.release(virtualAddress,count);
    }else if(type==AddressPoolType::USER){
        programManager.running->userVirtual.release(virtualAddress,count);
    }
}